package com.agg.persist.xml;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.agg.core.exception.EntityMapperException;
import com.agg.core.utils.Asserts;
import com.agg.core.utils.Beans;
import com.agg.core.utils.Strings;
import com.agg.domain.specification.Specification;
import com.agg.persist.xml.mapping.*;
import com.baomidou.mybatisplus.annotation.TableField;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.*;

public class EntityMapperRegistry extends XmlBuildFactoryTemplate {

    protected static Map<String, EntityMapper>  entityMapperMap = new HashMap<>();

    private static  final String SPACE=" ";

    private static final String POINT=".";

    protected final Log logger = LogFactory.getLog(getClass());

    /**
     * The default constructor.
     */
    public EntityMapperRegistry(String  path) {
        if(entityMapperMap!=null && !entityMapperMap.isEmpty()) {
            return;
        };
        init(path);
    }
    private void init(String path){
        //初始化mapper,将xml转为mapper
        boolean isSuccess= initFactory(path);
        if(isSuccess){
            //绑定关联关系
            build();
        }
    }


    /**
     *  build join and  sqlmapper
     */
    private void build(){
        for(Map.Entry<String,EntityMapper> entry:entityMapperMap.entrySet()){
            EntityMapper em= entry.getValue();
            //build join relation
            buildJoin(em);
            //build ref action
            buildRef(em);
        }
    }

    /**
     *  build join and  sqlmapper
     */
    public   void build(Map<String, EntityMapper>  emMap){
        for(Map.Entry<String,EntityMapper> entry:emMap.entrySet()){
            EntityMapper em= entry.getValue();
            //cache entity mapper
            this.entityMapperMap.put(entry.getKey(),em);
        }
        for(Map.Entry<String,EntityMapper> entry:emMap.entrySet()) {
            EntityMapper em= entry.getValue();
            //build join relation
            buildJoin(em);
            //build ref action
            buildRef(em);
        }

    }


    /**
     * build join relation;
     * @param em
     */
    private void buildJoin(EntityMapper em){
        List<Join> joins=em.getJoins();
        if(Asserts.isNull(joins)){
            return;
        }
        List<Property> all=new ArrayList<Property>();
        all.addAll(em.getProperties());
        String specificationName=null;
        try {
            for(Join join:joins){
                String mapperId=join.getMapperId();
                if(entityMapperMap.containsKey(mapperId)){
                    EntityMapper jem= entityMapperMap.get(mapperId);
                    //name 为空则关联查询数据
                    checkPropertyExist(all,jem);
                    all.addAll(jem.getProperties());
                    join.setEntityMapper(jem);
                    join.setLeftOwner(em.getAlias());
                    join.setRightOwner(jem.getAlias());
                    specificationName=join.getSpecificationName();
                    if(Asserts.isNotNullOrEmpty(specificationName)){
                        Class specClz=Class.forName(specificationName);
                        join.setSpecification((Specification)Beans.getBean(specClz));
                    }

                }
            }
        }catch (ClassNotFoundException e){
            String errMsg="specificationName "+specificationName+" cannot  found";
            logger.error(errMsg,e);
            throw  new EntityMapperException(errMsg);
        }
    }


    private void buildRef(EntityMapper em) {
        List<Ref> refs=em.getRefs();
        if(Asserts.isNull(refs)){
            return ;
        }
        String specificationName=null;
        try {
            for(Ref ref:refs){
                String bean=ref.getBean();
                Class beanClz=Class.forName(bean);
                ref.setExecutor(Beans.getBean(beanClz));
                if(Asserts.isNotNullOrEmpty(specificationName)){
                    Class specClz=Class.forName(specificationName);
                    ref.setSpecification((Specification)Beans.getBean(specClz));
                }
            }
        }catch (ClassNotFoundException e){
            String errMsg="specificationName "+specificationName+" cannot  found";
            logger.error(errMsg,e);
            throw  new EntityMapperException(errMsg);

        }catch (Exception e){
            String errMsg="初始化 entity mapper ref 系统错误："+specificationName;
            logger.error(errMsg,e);
            throw  new EntityMapperException(errMsg);
        }

    }


    /**
     * 检测属性是否定义重复
     * @param all
     * @param jem
     */
    private  void checkPropertyExist(List<Property> all, EntityMapper jem){
        Long exist;
        List<Property> properties=jem.getProperties();
        for(Property prop:properties){
            String alias=prop.getAlias();
            String name=Asserts.isNullOrEmpty(alias)?prop.getName():alias;
            exist= all.stream().filter(p->{
                String existName=Asserts.isNullOrEmpty(p.getAlias())?p.getName():p.getAlias();
               return existName.equals(name);
            }).count();
            if(exist>0){
                String err="mapperId = "+jem.getId()+" property name = {"+prop.getName()+"} duplicate";
                logger.error(err);
                throw  new EntityMapperException(err);
            }
        }

    }

    /**
     * get the configure of the certain entity .
     * @param id the class name of the entity
     * @return the configure of the entity
     */
    public static EntityMapper getEntityMapper(String id) {
        EntityMapper entityMapper= entityMapperMap.get(id);
        if(entityMapper==null) throw new EntityMapperException("Not found the entity mapper["+id+"] in the mapper xml!");
        return entityMapper;
    }

    /**
     * decode a entity configure and load into the factory.
     * @param element
     */
    @Override
    protected void loadBean(Element element) {
        EntityMapper em = new EntityMapper();
        String clazz = element.getAttribute("class");
        em.setClazz(clazz);
        String tableName = element.getAttribute("tableName");
        em.setTable(tableName);
        String id=element.getAttribute("id");
        em.setId(id);
        String alias=element.getAttribute("alias");
        em.setAlias(alias);
        loadChildNodes(element, em,null);
        if(entityMapperMap.containsKey(id)){
            throw new EntityMapperException("id:"+id+" duplicate");
        }
        entityMapperMap.put(id,em);

    }

    /**
     * decode a entity configure and load into the factory.
     * @param element
     */
    @Override
    protected void loadBean(Element element,Map<String,List<Property>> propGroup) {
        EntityMapper em = new EntityMapper();
        String clazz = element.getAttribute("class");
        em.setClazz(clazz);
        String tableName = element.getAttribute("tableName");
        em.setTable(tableName);
        String id=element.getAttribute("id");
        em.setId(id);
        String alias=element.getAttribute("alias");
        em.setAlias(alias);
        loadChildNodes(element, em,propGroup);
        if(entityMapperMap.containsKey(id)){
            throw new EntityMapperException("id:"+id+" duplicate");
        }
        entityMapperMap.put(id,em);

    }




    private String getAlias(EntityMapper em){
        String alias=Asserts.isNullOrEmpty(em.getAlias())?em.getTable():em.getAlias();
        return alias;
    }

    /**
     * load all of the child nodes into entity.
     * @param element
     * @param em
     */
    private void loadChildNodes(Element element,EntityMapper em,Map<String,List<Property>> propGroup) {
        NodeList nodeList = element.getChildNodes();
        Class clazz=null;
        try{
             clazz=Class.forName(em.getClazz());
        }catch (ClassNotFoundException e){
            throw new EntityMapperException("entity not found");
        }
        Map<String,Field> fieldMap=ReflectUtil.getFieldMap(clazz);
        for(int i=0; i<=nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if(!(node instanceof Element)){continue;}
            if (node.getNodeName().equals("property")) {
                Property property = getProperty((Element) node);
                if(!fieldMap.containsKey(property.getName())){
                    throw new EntityMapperException("property name :"+property.getName()+" not found in :"+em.getClazz());
                }
                property.setOwner(em.getAlias());
                em.getProperties().add(property);
            }
            //将include  关联的属性集，放入映射对象中
            if(propGroup!=null&&node.getNodeName().equals("include")){
                Element inclueEle=(Element) node;
                String includeId=inclueEle.getAttribute("refId");
                if(!propGroup.containsKey(includeId)){
                    throw new EntityMapperException("include id="+includeId+" not found");
                }
                List<Property> properties=propGroup.get(includeId);
                if(properties!=null){
                    for(Property property:properties){
                        if(!fieldMap.containsKey(property.getName())){
                            throw new EntityMapperException("property name :"+property.getName()+" not found in :"+em.getClazz());
                        }

                        //直接复制生成新对象，避免因引用修改属性导致数据不一致，例如设置owner
                        Property copy= BeanUtil.copyProperties(property,Property.class);
                        copy.setOwner(em.getAlias());
                        //如果当前mapper属性已定义，则不能重复
                        Long count=em.getProperties().stream()
                                .filter(p->p.getName().equals(property.getName()))
                                .count();
                        if(count==0){
                            em.getProperties().add(copy);
                        }

                    }
                }
            }
            if (node.getNodeName().equals("join")) {
                Join join = getJoin((Element) node);
                em.getJoins().add(join);
            }
            if (node.getNodeName().equals("ref")) {
                Ref ref = getRef((Element) node);
                em.getRefs().add(ref);
            }
        }
        if(em.getProperties().isEmpty()){
            //如果无property节点或include 节点，默认取实体属性封装为节点
            em.getProperties().addAll(getPropertysByClazz(em));
        }
    }

    /**
     * 获取全部的字段
     * @param em
     * @return
     * @throws ClassNotFoundException
     */
    private List<Property> getPropertysByClazz(EntityMapper em)  {
        List<Property>  list= new ArrayList<>();
        try{
            Class clz=Class.forName(em.getClazz());
            Field[] fields=ReflectUtil.getFields(clz);
            for (Field field:fields){
                TableField tf=field.getAnnotation(TableField.class);
                if(tf!=null&&!tf.exist()){
                    continue;
                }
                Property property=new Property();
                property.setName(field.getName());
                property.setOwner(em.getAlias());
                String column= Strings.toLowerCase(field.getName(),"_");
                property.setColumn(column);
                //主键
                if(field.getName().equals("id")){
                    property.setPrimaryKey(true);
                }
                //初始化查询类型：字符串为like,时间类型为时间区间
                if(field.getType()==String.class){
                    property.setQuery(QueryEnum.like.name());
                }else if (field.getType()==Date.class||field.getType()==LocalDateTime.class){
                    property.setQuery(QueryEnum.time.name());
                }else{
                    property.setQuery(QueryEnum.eq.name());
                }
                list.add(property);
            }
        }catch (ClassNotFoundException e){
            throw  new EntityMapperException(em.getClazz()+",找不到实体类");
        }
        return list;

    }




    /**
     * get join tag from xml.
     * @param element
     * @return join
     */
    private Join getJoin(Element element) {
        Join join = new Join();
        String name = element.getAttribute("name");
        join.setName(name);
        String mapperId = element.getAttribute("mapperId");
        join.setMapperId(mapperId);
        String leftKey = element.getAttribute("leftKey");
        join.setLeftKey(leftKey);
        String rightKey = element.getAttribute("rightKey");
        join.setRightKey(rightKey);
        String joinType = element.getAttribute("joinType");
        join.setJoinType(joinType);
        String clazz = element.getAttribute("class");
        join.setClazz(clazz);
        boolean isAggregation = "true".equals(element.getAttribute("isAggregation"));
        join.setAggregation(isAggregation);
        String specificationName = element.getAttribute("specificationName");
        join.setSpecificationName(specificationName);
        return join;
    }

    /**
     * get ref tag from xml.
     * @param element
     * @return ref
     */
    private Ref getRef(Element element) {
        Ref ref = new Ref();
        String name = element.getAttribute("name");
        ref.setName(name);
        String refKey = element.getAttribute("refKey");
        ref.setRefKey(refKey);
        String refType = element.getAttribute("refType");
        ref.setRefType(refType);
        String bean = element.getAttribute("bean");
        ref.setBean(bean);
        String method = element.getAttribute("method");
        ref.setMethod(method);
        String listMethod = element.getAttribute("listMethod");
        ref.setListMethod(listMethod);
        String specificationName = element.getAttribute("specificationName");
        ref.setSpecificationName(specificationName);
        return ref;
    }

    public static Map<String, EntityMapper> getEntityMapperMap() {
        return entityMapperMap;
    }



    /**
     * 获取sql
     * @param mapperId
     * @return
     */
    public static  String sqlTpl(String mapperId){
        if(Asserts.isNullOrEmpty(mapperId)
               ||!entityMapperMap.containsKey(mapperId)) {
            return null;
        }
        EntityMapper em=entityMapperMap.get(mapperId);
        return em.getSqlTpl();
    }




}
